#include "Config.h"
#include <math.h>
#include "QueueNetworkModel.h"
#include "QueueNetworkModelBuilder.h"
#include "Clustering.h"
#include "read.h"
#include "request.h"

#include "lib/csim.h"
#include "hashtable.h"
#include "StochasticModel.h"
#include "Transient.h"

void sim();
void webClient(PartitionedRequest * pr);
void traceDrivenSimulation();
void stochasticSimulation();
void initializeRunLengthControl();
void initializeSimulation();
void initializeStochasticSimulation();
int convergenceIsAchieved();

void sim(){

#if STOCHASTIC_SIMULATION
    stochasticSimulation();
#endif

#if TRACE_DRIVEN_SIMULATION
    traceDrivenSimulation();
#endif
    
}

void initializeRunLengthControl(){
    int i;
    for(i=0; i<N_LOCAL_AREA; i++){
        table_run_length(network_facility->local_areas[i]->local_area_response_time, 0.05, 0.9, 100000.0);
    }
}

static int ITERATION_COUNT = 0;
static int IS_RESETTED = 0;

int convergenceIsAchieved(int index) {
    if (state(converged) == OCC) {
        wait(converged);
        if (IS_RESETTED) {
            ITERATION_COUNT++;
            if (ITERATION_COUNT == 4) {
                printf("Simulation Converged at time: %f \n\n", simtime());
                return 1;
            }
        }
    }

#if EXCLUDE_TRANSIENT > 0
    if ((index > EXCLUDE_TRANSIENT) && (!IS_RESETTED)) {
        printf("Reset statistics = %d\n\n", index);
        reset();
        IS_RESETTED = 1;
    }
#endif
    return 0;
}

void initializeSimulation(){
    collect_class_facility_all();
    buildQueueNetworkModel();
    readLog();

#if TRACE_DRIVEN_SIMULATION
    computeSizeProbability();
    computeKMeansClustering(N_CLASS);
#endif

    max_processes(MAX_PROCESSES);

#if RECORD_OSSERVATION
    beginObservations();
#endif

}

void initializeStochasticSimulation(){
    initializeRunLengthControl();

#if STOCHASTIC_SERVER
    initializeStochasticServer();
#endif

#if STOCHASTIC_SIZE
    initializeStochasticSize();
#else
    computeSizeProbability();
    computeKMeansClustering(N_CLASS);
#endif
    
#if STOCHASTIC_INTERARRIVAL_TIME
    initializeStochasticInterarrivalTime();
#endif

}

void stochasticSimulation() {
    initializeSimulation();
    initializeStochasticSimulation();
    create("sim");

    int i=0;
    int n_class = STOCHASTIC_CLUSTERIZED_SIZE ? N_CLASS_STOCHASTIC : N_CLASS;
    double interarrival_time = 0;

    hold(interarrival_time);

#if STOCHASTIC_SERVER
    p_request[i].server = nextStochasticServer();
#endif

#if STOCHASTIC_SIZE
    p_request[i].size = nextStochasticSize();
    //calcola la classe di appartenenza del nuovo size stocastico generato
    getClusterMembership(&p_request[i], cluster, n_class);

#endif

    webClient(&p_request[0]);

    int x = N_P_REQUEST/50;
    int perc = 0;

    for (i=1; i<N_P_REQUEST; i++) {
        if (i%x == 0) {
            (perc%10 == 0)? printf("%d%%", perc):printf(".");
            perc += 2;
        }

        if (convergenceIsAchieved(i))
            break;
#if STOCHASTIC_SERVER
        p_request[i].server = nextStochasticServer();
#endif

#if STOCHASTIC_SIZE
        p_request[i].size = nextStochasticSize();
        //calcola la classe di appartenenza del nuovo size stocastico generato
        getClusterMembership(&p_request[i], cluster, n_class);
#endif
        
#if STOCHASTIC_INTERARRIVAL_TIME
        interarrival_time = nextStochasticInterarrivalTime();
#else
        interarrival_time = (double) p_request[i].timestamp - p_request[i-1].timestamp;
#endif

        hold(interarrival_time);
        webClient(&p_request[i]);
    }
    printf("\n\n");
    report();

    computeNetworkStatistics();

    printNetworkFacilityPerformanceIndex();

    printLocalAreaFacilityPerformanceIndex();

}

void traceDrivenSimulation(){
    initializeSimulation();

    create("sim");

    int i;
    double interarrival_time = 0;
    hold(interarrival_time);
    webClient(&p_request[0]);

    int x = N_P_REQUEST/50;
    int perc = 0;
    for (i=1; i<N_P_REQUEST; i++) {
        if (i%x == 0) {
            (perc%10 == 0)? printf("%d%%", perc):printf(".");
            perc += 2;
        }

        if (convergenceIsAchieved(i))
            break;

        interarrival_time = (double) p_request[i].timestamp - p_request[i-1].timestamp;
        
        hold(interarrival_time);

        webClient(&p_request[i]);
    }
    printf("\n\n");

    report();

    computeNetworkStatistics();

    printNetworkFacilityPerformanceIndex();

    printLocalAreaFacilityPerformanceIndex();

}

void webClient(PartitionedRequest * pr){
	//sprintf(STR_BUFFER, "WebClient_%ld", N_CLIENT);
	N_CLIENT++;
	create("client");

	useNetworkFacility(pr);

	csim_terminate();
}